/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.munchkinquest.beans;

import com.munchkinquest.Constants;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.munchkinquest.model.Btn;
import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.persistence.EntityManagerFactory;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceUnit;
import com.munchkinquest.model.Instruction;
import com.munchkinquest.model.Method;
import com.munchkinquest.model.Navtext;
import com.munchkinquest.model.Note;
import com.munchkinquest.model.Stepbtn;
import com.munchkinquest.model.Step;
import com.munchkinquest.model.Stepnote;
import com.munchkinquest.utils.ButtonManager;
import com.munchkinquest.utils.SpecialSteps;
import com.munchkinquest.utils.StringUtils;
import javax.persistence.EntityManager;

/**
 *
 * @author stephen
 */
@ManagedBean
@SessionScoped
public class StepBean {

    private static final int BTN_NEXT = 0;
    private static final int FIRST_BTN = 0;
    private static final int SECOND_BTN = 1;
    private static final int TYPE_MANYBTNS = 3;
    private static final int TYPE_NEXT = 1;
    private static final int TYPE_YESNO = 2;
    private static final int TYPE_METHOD = 4;
    public static final String UTILSBUTTONMANAGER = "com.munchkinquest.utils.ButtonManager";
    @PersistenceUnit
    EntityManagerFactory emf;
    private boolean stepChanged = true;
    private Step step;
    private Stepbtn[] stepBtns;
    private Note[] notes;
    private String instruction;
    private Player player;

    public Player getPlayer() {
        return player;
    }

    public void setPlayer(Player player) {
        this.player = player;
    }

    public StepBean() {
        player = new Player();
    }

    @PostConstruct
    public void reset() {
        if (step == null) {
            nextStepId = SpecialSteps.getSpecialStep(Constants.START);
            initialise();
            loadAll(nextStepId);
            processBtn(stepBtns[0]);
        }
    }
    protected long nextStepId;
    protected boolean isNextBtn = true;
    protected String nextBtnText;
    protected boolean isYesNoBtns = false;
    protected boolean isMethodBtns;
    protected String yesBtnText = "";
    protected String noBtnText = "";
    protected boolean showNavText;
    protected String navText;
    protected boolean isManyButtons;
    protected String btnChoice;
    protected boolean showNoNotes;
    protected boolean showNotes;
    protected String healthList;
    protected int movement;
    protected int health;
    protected int totalHealth;
    protected int totalMovement;

    public int getTotalMovement() {
        return player.getTotalMovement();
    }

    public void setTotalMovement(int totalMovement) {
        player.setTotalMovement(totalMovement);
    }

    public int getTotalHealth() {
        return player.getTotalHealth();
    }

    public void setTotalHealth(int totalHealth) {
        player.setTotalHealth(totalHealth);
    }

    public int getHealth() {
        return player.getHealth();
    }

    public void setHealth(int health) {
        player.setHealth(health > player.getTotalHealth() ? player.getHealth() : health);
    }

    public int getMovement() {
        return player.getMovement();
    }

    public void setMovement(int movement) {
        player.setMovement(movement > player.getTotalMovement() ? player.getTotalMovement() : movement);
    }

    public String getMovementList() {
        return player.getMovementList();
    }

    public String getHealthList() {
        return player.getHealthList();
    }

    public boolean isShowNotes() {
        return notes.length != 0;
    }

    public boolean isShowNoNotes() {
        return notes.length == 0;
    }

    public String getBtnChoice() {
        return btnChoice;
    }

    public void setBtnChoice(String btnChoice) {
        this.btnChoice = btnChoice;
    }

    public Stepbtn[] getStepBtns() {
        return stepBtns;
    }

    public String getNavText() {
        return instruction.isEmpty() ? navText : "<hr/>" + navText;
    }

    public void setNavText(String navText) {
        this.navText = navText;
    }

    public boolean isShowNavText() {
        return navText != null;
    }

    public String doNextBtn() {
        try {
            processBtn(stepBtns[0]);
        } catch (RuntimeException e) {
            StringUtils.throwMessage(e.getMessage());
        }
        return "";
    }

    public String doFirstBtn() {
        String page = "";
        try {
            page = processBtn(stepBtns[FIRST_BTN]);
        } catch (RuntimeException e) {
            StringUtils.throwMessage(e.getMessage());
        }
        return page;
    }

    public String doSecondBtn() {
        try {
            processBtn(stepBtns[SECOND_BTN]);
        } catch (RuntimeException e) {
            StringUtils.throwMessage(e.getMessage());
        }
        return "";
    }

    public String doManyBtns() {
        String page = "";
        try {
            if (btnChoice == null || btnChoice.isEmpty()) {
                StringUtils.throwMessage("Choose an option");
            } else {
                Stepbtn btn = findBtn(Integer.parseInt(btnChoice));
                if (btn == null) {
                    StringUtils.throwMessage("Couldn't find button object");
                } else {
                    page = processBtn(btn);
                }
            }
        } catch (NumberFormatException e) {
            StringUtils.throwMessage(btnChoice + "is not a Long Value");
        } catch (RuntimeException e) {
            StringUtils.throwMessage(e.getMessage());
        }

        return page;
    }

    private String processBtn(Stepbtn btn) {
        StringBuilder page = new StringBuilder("");
        try {
            if (btn.getMethodid() == null) {
                nextStepId = btn.getNextstepid();
                loadAll(nextStepId);
            } else if (btn.getMethodName().isEmpty()) {
                throw new RuntimeException("Missing method name from button");
            } else {
                Long nextId = (Long) invokeSpecifiedMethod(btn, page);
                if (nextId == 0) {
                    return page.toString();
                } else {
                    nextStepId = nextId;
                    loadAll(nextStepId);
                }

            }
            return page.toString();

        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(ex.getMessage());
        } catch (InstantiationException ex) {
            throw new RuntimeException(ex.getMessage());
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(ex.getMessage());
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(ex.getMessage());
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex.getMessage());
        } catch (InvocationTargetException ex) {
            throw new RuntimeException(ex.getMessage());
        }
    }

    private Object invokeSpecifiedMethod(Stepbtn sb, StringBuilder page) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
        Object result = 0L;
        Class<?> c = Class.forName(UTILSBUTTONMANAGER);
        Object obj = c.newInstance();
        Class[] par = new Class[3];
        par[0] = Stepbtn.class;
        par[1] = Player.class;
        par[2] = StringBuilder.class;
        java.lang.reflect.Method method = c.getDeclaredMethod(sb.getMethodName(), par);
        result = method.invoke(obj, sb, player, page);
        return result;
    }

    public String getInstruction() {
        return instruction;
    }

    public String getFirstBtnText() {
        return stepBtns[FIRST_BTN].getBtnText();
    }

    public String getSecondBtnText() {
        return isIsYesNoBtns() ? stepBtns[SECOND_BTN].getBtnText() : "";
    }

    public boolean isIsYesNoBtns() {
        return step.getSteptypeid() == TYPE_YESNO;
    }

    public String getNextBtnText() {
        return isNextBtn ? stepBtns[BTN_NEXT].getBtnText() : "";
    }

    public boolean isIsNextBtn() {
        return step.getSteptypeid() == TYPE_NEXT;
    }

    public boolean isIsManyButtons() {
        return step.getSteptypeid() == TYPE_MANYBTNS;
    }

    public boolean isIsMethodBtns() {
        return step.getSteptypeid() == TYPE_METHOD;
    }

    public String getNotes() {
        String result = "<h2>Notes</h2>";
        for (Note each : notes) {
            result += "<p>" + StringUtils.getFormattedNote(each.getTitle(), each.getBody()) + "</p>";
        }
        return result;
    }

    public boolean isStepIdChanged() {
        return stepChanged;
    }

    public void setStepIdChanged(boolean stepChanged) {
        this.stepChanged = stepChanged;
    }

    private void loadAll(long stepId) {
        //TODO check for step already loaded
        try {
            loadStep(stepId);
            loadInstruction(step.getInstructionid());
            loadBtns(step.getId());
            loadNotes(step.getId());
            loadNavText(step.getNavtextid());
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
            StringUtils.throwMessage(e.getMessage());
        }
    }

    private void loadStep(long stepId) {
        //TODO check for step already loaded
        try {
            step = (Step) emf.createEntityManager().createNamedQuery("Step.findById").
                    setParameter("id", stepId).getSingleResult();
        } catch (NoResultException e) {
            throw new RuntimeException("Could not find Step with id = " + stepId);
        }
    }

    private void loadInstruction(long instrId) {
        try {
            instruction = ((Instruction) emf.createEntityManager().
                    createNamedQuery("Instruction.findById").setParameter("id", instrId).
                    getSingleResult()).getDescription();
        } catch (NoResultException e) {
            throw new RuntimeException("Could not find Instruction with id = " + instrId);
        }

    }

    private void loadBtns(long stepId) {
        try {
            List<Stepbtn> stepBtnList = emf.createEntityManager().
                    createNamedQuery("Stepbtn.findByStepid").setParameter("stepid", stepId).
                    getResultList();


            stepBtns = new Stepbtn[stepBtnList.size()];
            int i = 0;
            for (Stepbtn stepBtn : stepBtnList) {
                stepBtns[i] = stepBtn;

                Btn btn = (Btn) emf.createEntityManager().createNamedQuery("Btn.findById").
                        setParameter("id", stepBtns[i].getBtnid()).getSingleResult();
                stepBtns[i].setBtnText(btn.getBtntext());
                if (stepBtns[i].getMethodid() != null) {
                    System.out.println("METHOD ID = " + stepBtns[i].getMethodid());
                    Method method = (Method) emf.createEntityManager().
                            createNamedQuery("Method.findById").
                            setParameter("id", stepBtns[i].getMethodid()).getSingleResult();
                    if (method == null) {
                        StringUtils.throwMessage("Couldn't find method");
                    } else {
                        stepBtns[i].setMethodName(method.getName());
                    }
                }
                i++;
            }
        } catch (NoResultException e) {
            throw new RuntimeException("Loading Btns: " + e);
        }
    }

    private void loadNotes(long stepId) {
        try {
            EntityManager em = emf.createEntityManager();
            List<Stepnote> stepNoteList = em.createNamedQuery("Stepnote.findByStepid").setParameter("stepid", stepId).
                    getResultList();


            notes = new Note[stepNoteList.size()];
            int i = 0;
            for (Stepnote stepNote : stepNoteList) {
                Note note = (Note) emf.createEntityManager().createNamedQuery("Note.findById").
                        setParameter("id", stepNote.getNoteid()).getSingleResult();
                notes[i++] = note;
            }
        } catch (NoResultException e) {
            throw new RuntimeException("Loading Notes: Could not find StepBtn with Step id = " + stepId);
        }

    }

    private void loadNavText(Long id) {
        navText = "";
        try {
            if (id != null) {
                navText = ((Navtext) emf.createEntityManager().createNamedQuery("Navtext.findById").setParameter("id", id).
                        getSingleResult()).getText();
            }
        } catch (NoResultException e) {
            throw new RuntimeException("Could not find NavText with id = " + id);
        }
    }

    public String doReset() {
        player.reset();
        nextStepId = SpecialSteps.getSpecialStep(Constants.START);
        loadAll(nextStepId);
        processBtn(stepBtns[0]);
        return "";
    }

    private Stepbtn findBtn(int id) {
        Stepbtn result = null;
        for (Stepbtn btn : stepBtns) {
            if (btn.getBtnid() == id) {
                result = btn;
            }
        }
        return result;
    }

    public void doDie() {
        player.die();
        loadAll(SpecialSteps.getSpecialStep(Constants.DIE));
    }

    public void doLowerHealth() {
        player.lowerHealth();
        if (player.isDead()) {
            doDie();
        }
    }

    private void initialise() {
        health = player.getHealth();
    }

    public String startGuide() {
        nextStepId = SpecialSteps.getSpecialStep(Constants.START);
        loadAll(nextStepId);
        processBtn(stepBtns[0]);
        return "step";
    }

    public String setDefaults() {
        player.setDefaults();
        return "";
    }

    public String doEndTurn() {
        player.endTurn();
        nextStepId = SpecialSteps.getSpecialStep(Constants.MONSTERS_TURN);
        loadAll(nextStepId);
        return "";
    }

    public boolean isShowCounters() {
        return (step.getId() == SpecialSteps.getSpecialStep(Constants.COMBAT)
                || step.getId() == SpecialSteps.getSpecialStep(Constants.ROLL_FOR_COMBAT));
    }
}
